Scan(tree) only inspected top-level SELECT WHERE/HAVING/columns/FROM,
INSERT VALUES rows, UPDATE/DELETE WHERE and set-operation leaves, so
time-based, out-of-band and tautology patterns inside CTEs, subqueries,
derived tables, CASE/CAST/IN/BETWEEN, ORDER/GROUP BY, JOIN ON, window
specs, FILTER, LIMIT, LATERAL/APPLY, PREWHERE, QUALIFY, CONNECT BY,
RETURNING, ON CONFLICT, INSERT ... SELECT, MERGE, CREATE TABLE AS,
CREATE [MATERIALIZED] VIEW and EXPLAIN went undetected while ScanSQL
caught them.
Scan now uses ast.Inspect: every FunctionCall and table function is
checked once, tautologies are checked in every condition position, UNION
checks run on each outermost set operation (including those nested in
CTEs and subqueries), and text-bodied nodes keep the text path. JOIN
Left references are skipped to avoid double counting, and qualified
identifiers such as a.id = b.id are no longer reported as tautologies.
T-SQL IF ... WAITFOR and standalone VALUES were already covered on main
(#871) and gain regression tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2
Summary
Scanner.Scan(tree)only inspected top-level SELECT WHERE/HAVING/columns/FROM, INSERT VALUES rows, UPDATE/DELETE WHERE and set-operation leaves. Time-based, out-of-band and tautology patterns nested anywhere else were missed, whileScanSQLcaught them. On origin/main, 37 of 59 probe queries showed this gap. Missed locations included CTEs, derived tables, subqueries, CASE/CAST/IN/BETWEEN/ANY, ORDER/GROUP BY, JOIN ON, window specs, FILTER, LIMIT, LATERAL/APPLY, PREWHERE, QUALIFY, CONNECT BY, RETURNING, ON CONFLICT, INSERT ... SELECT, MERGE, CREATE TABLE AS, CREATE [MATERIALIZED] VIEW and EXPLAIN.Scannow usesast.Inspect. It checks everyFunctionCalland table function once and checks tautologies in every condition position (WHERE, HAVING, QUALIFY, PREWHERE, START WITH/CONNECT BY, JOIN ON/MATCH_CONDITION, MERGE ON/WHEN, CASE WHEN, FILTER, ON CONFLICT WHERE). UNION checks now also run on outermost set operations nested in CTEs and subqueries. Text-bodied nodes (Values, Utility, Set, Transaction, Use, Unsupported) still use the text path and are pruned from the AST walk, so no occurrence is reported twice.JoinClause.Leftis skipped because it repeats an earlier reference.a.id = b.idare no longer reported as tautologies. Before this change only the column name was compared.IF ... WAITFOR DELAYand standaloneVALUESwere already detected on main (fix(security,cbinding): scan raw-text statements, recover from panics in C exports #871). They now have regression tests.Test Plan
pkg/sql/security/ast_coverage_test.go:Scanreports everythingScanSQLreports.task check(fmt, vet, golangci-lint 0 issues, test:race): pass, no FAIL lines.task test:cbinding: pass.-count=5 -benchmem, before (origin/main) vs after. TypicalScancost rose from about 25–200 ns to about 160–1250 ns, with 7–16 allocations per call. Most of that comes from allocating theChildren()slices during the walk. The old cost was low because the scanner skipped most of the tree. The scanner is not a tokenizer/parser/formatter hot path, and parsing the same query costs several microseconds, so this is accepted as the cost of complete coverage. The two text-based benchmarks did not change significantly (DangerousFunctions −14%, TimeBasedInjection no change).Breaking Changes
None to the API.
Scannow returns more findings for queries with nested dangerous patterns. It returns fewer for qualified column equalities (a.id = b.id), which are no longer reported as tautologies.🤖 Generated with Claude Code
https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2